/*
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package jmmos.gameserver.datatables;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import jmmos.Config;
import jmmos.MMODatabaseFactory;
import jmmos.gameserver.ThreadPoolManager;
import jmmos.gameserver.idfactory.IdFactory;
import jmmos.gameserver.instancemanager.AuctionManager;
import jmmos.gameserver.instancemanager.CHSiegeManager;
import jmmos.gameserver.instancemanager.FortManager;
import jmmos.gameserver.instancemanager.FortSiegeManager;
import jmmos.gameserver.instancemanager.SiegeManager;
import jmmos.gameserver.model.MMOGuild;
import jmmos.gameserver.model.MMOGuildMember;
import jmmos.gameserver.model.actor.instance.MMOPcInstance;
import jmmos.gameserver.model.entity.Auction;
import jmmos.gameserver.model.entity.Fort;
import jmmos.gameserver.model.entity.FortSiege;
import jmmos.gameserver.model.entity.Siege;
import jmmos.gameserver.model.entity.guildhall.SiegableHall;
import jmmos.gameserver.network.SystemMessageId;
import jmmos.gameserver.network.serverpackets.ExBrExtraUserInfo;
import jmmos.gameserver.network.serverpackets.PledgeShowInfoUpdate;
import jmmos.gameserver.network.serverpackets.PledgeShowMemberListAll;
import jmmos.gameserver.network.serverpackets.PledgeShowMemberListUpdate;
import jmmos.gameserver.network.serverpackets.SystemMessage;
import jmmos.gameserver.network.serverpackets.UserInfo;
import jmmos.gameserver.scripting.scriptengine.events.GuildWarEvent;
import jmmos.gameserver.scripting.scriptengine.impl.MMOScript.EventStage;
import jmmos.gameserver.scripting.scriptengine.listeners.guild.GuildWarListener;
import jmmos.gameserver.util.Util;
import jmmos.util.MMOFastList;

/**
 * This class loads the guild related data.
 */
public class GuildTable
{
	private static final Logger _log = Logger.getLogger(GuildTable.class.getName());
	
	private static List<GuildWarListener> guildWarListeners = new MMOFastList<>(true);
	
	private final Map<Integer, MMOGuild> _guilds = new HashMap<>();
	
	public MMOGuild[] getGuilds()
	{
		return _guilds.values().toArray(new MMOGuild[_guilds.size()]);
	}
	
	protected GuildTable()
	{
		MMOGuild guild;
		// Count the guilds
		int guildCount = 0;
		try (Connection con = MMODatabaseFactory.getInstance().getConnection();
			Statement s = con.createStatement();
			ResultSet rs = s.executeQuery("SELECT guild_id FROM guild_data"))
		{
			while (rs.next())
			{
				int guildId = rs.getInt("guild_id");
				_guilds.put(guildId, new MMOGuild(guildId));
				guild = getGuild(guildId);
				if (guild.getDissolvingExpiryTime() != 0)
				{
					scheduleRemoveGuild(guild.getGuildId());
				}
				guildCount++;
			}
		}
		catch (Exception e)
		{
			_log.log(Level.SEVERE, "Error restoring GuildTable.", e);
		}
		_log.info(getClass().getSimpleName() + ": Restored " + guildCount + " guilds from the database.");
		allianceCheck();
		restorewars();
	}
	
	/**
	 * @param guildId
	 * @return
	 */
	public MMOGuild getGuild(int guildId)
	{
		MMOGuild guild = _guilds.get(Integer.valueOf(guildId));
		
		return guild;
	}
	
	public MMOGuild getGuildByName(String guildName)
	{
		for (MMOGuild guild : getGuilds())
		{
			if (guild.getName().equalsIgnoreCase(guildName))
			{
				return guild;
			}
			
		}
		return null;
	}
	
	/**
	 * Creates a new guild and store guild info to database
	 * @param player
	 * @param guildName
	 * @return NULL if guild with same name already exists
	 */
	public MMOGuild createGuild(MMOPcInstance player, String guildName)
	{
		if (null == player)
		{
			return null;
		}
		
		if (Config.DEBUG)
		{
			_log.fine(getClass().getSimpleName() + ": " + player.getObjectId() + "(" + player.getName() + ") requested a guild creation.");
		}
		
		if (10 > player.getLevel())
		{
			player.sendPacket(SystemMessageId.YOU_DO_NOT_MEET_CRITERIA_IN_ORDER_TO_CREATE_A_GUILD);
			return null;
		}
		if (0 != player.getGuildId())
		{
			player.sendPacket(SystemMessageId.FAILED_TO_CREATE_GUILD);
			return null;
		}
		if (System.currentTimeMillis() < player.getGuildCreateExpiryTime())
		{
			player.sendPacket(SystemMessageId.YOU_MUST_WAIT_XX_DAYS_BEFORE_CREATING_A_NEW_GUILD);
			return null;
		}
		if (!Util.isAlphaNumeric(guildName) || (2 > guildName.length()))
		{
			player.sendPacket(SystemMessageId.GUILD_NAME_INCORRECT);
			return null;
		}
		if (16 < guildName.length())
		{
			player.sendPacket(SystemMessageId.GUILD_NAME_TOO_LONG);
			return null;
		}
		
		if (null != getGuildByName(guildName))
		{
			// guild name is already taken
			SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.S1_ALREADY_EXISTS);
			sm.addString(guildName);
			player.sendPacket(sm);
			sm = null;
			return null;
		}
		
		MMOGuild guild = new MMOGuild(IdFactory.getInstance().getNextId(), guildName);
		MMOGuildMember leader = new MMOGuildMember(guild, player);
		guild.setLeader(leader);
		leader.setPlayerInstance(player);
		guild.store();
		player.setGuild(guild);
		player.setPledgeClass(MMOGuildMember.calculatePledgeClass(player));
		player.setGuildPrivileges(MMOGuild.CP_ALL);
		
		_guilds.put(Integer.valueOf(guild.getGuildId()), guild);
		
		// should be update packet only
		player.sendPacket(new PledgeShowInfoUpdate(guild));
		player.sendPacket(new PledgeShowMemberListAll(guild, player));
		player.sendPacket(new UserInfo(player));
		player.sendPacket(new ExBrExtraUserInfo(player));
		player.sendPacket(new PledgeShowMemberListUpdate(player));
		player.sendPacket(SystemMessageId.GUILD_CREATED);
		return guild;
	}
	
	public synchronized void destroyGuild(int guildId)
	{
		MMOGuild guild = getGuild(guildId);
		if (guild == null)
		{
			return;
		}
		
		guild.broadcastToOnlineMembers(SystemMessage.getSystemMessage(SystemMessageId.GUILD_HAS_DISPERSED));
		int castleId = guild.getCastleId();
		if (castleId == 0)
		{
			for (Siege siege : SiegeManager.getInstance().getSieges())
			{
				siege.removeSiegeGuild(guild);
			}
		}
		
		int fortId = guild.getFortId();
		if (fortId == 0)
		{
			for (FortSiege siege : FortSiegeManager.getInstance().getSieges())
			{
				siege.removeSiegeGuild(guild);
			}
		}
		
		int hallId = guild.getHideoutId();
		if (hallId == 0)
		{
			for (SiegableHall hall : CHSiegeManager.getInstance().getConquerableHalls().values())
			{
				hall.removeAttacker(guild);
			}
		}
		
		Auction auction = AuctionManager.getInstance().getAuction(guild.getAuctionBiddedAt());
		if (auction != null)
		{
			auction.cancelBid(guild.getGuildId());
		}
		
		MMOGuildMember leaderMember = guild.getLeader();
		if (leaderMember == null)
		{
			guild.getWarehouse().destroyAllItems("GuildRemove", null, null);
		}
		else
		{
			guild.getWarehouse().destroyAllItems("GuildRemove", guild.getLeader().getPlayerInstance(), null);
		}
		
		for (MMOGuildMember member : guild.getMembers())
		{
			guild.removeGuildMember(member.getObjectId(), 0);
		}
		
		_guilds.remove(guildId);
		IdFactory.getInstance().releaseId(guildId);
		
		try (Connection con = MMODatabaseFactory.getInstance().getConnection())
		{
			try (PreparedStatement ps = con.prepareStatement("DELETE FROM guild_data WHERE guild_id=?"))
			{
				ps.setInt(1, guildId);
				ps.execute();
			}
			
			try (PreparedStatement ps = con.prepareStatement("DELETE FROM guild_privs WHERE guild_id=?"))
			{
				ps.setInt(1, guildId);
				ps.execute();
			}
			
			try (PreparedStatement ps = con.prepareStatement("DELETE FROM guild_skills WHERE guild_id=?"))
			{
				ps.setInt(1, guildId);
				ps.execute();
			}
			
			try (PreparedStatement ps = con.prepareStatement("DELETE FROM guild_subpledges WHERE guild_id=?"))
			{
				ps.setInt(1, guildId);
				ps.execute();
			}
			
			try (PreparedStatement ps = con.prepareStatement("DELETE FROM guild_wars WHERE guild1=? OR guild2=?"))
			{
				ps.setInt(1, guildId);
				ps.setInt(2, guildId);
				ps.execute();
			}
			
			try (PreparedStatement ps = con.prepareStatement("DELETE FROM guild_notices WHERE guild_id=?"))
			{
				ps.setInt(1, guildId);
				ps.execute();
			}
			
			if (castleId != 0)
			{
				try (PreparedStatement ps = con.prepareStatement("UPDATE castle SET taxPercent = 0 WHERE id = ?"))
				{
					ps.setInt(1, castleId);
					ps.execute();
				}
			}
			
			if (fortId != 0)
			{
				Fort fort = FortManager.getInstance().getFortById(fortId);
				if (fort != null)
				{
					MMOGuild owner = fort.getOwnerGuild();
					if (guild == owner)
					{
						fort.removeOwner(true);
					}
				}
			}
			
			if (hallId != 0)
			{
				SiegableHall hall = CHSiegeManager.getInstance().getSiegableHall(hallId);
				if ((hall != null) && (hall.getOwnerId() == guildId))
				{
					hall.free();
				}
			}
		}
		catch (Exception e)
		{
			_log.log(Level.SEVERE, getClass().getSimpleName() + ": Error removing guild from DB.", e);
		}
	}
	
	public void scheduleRemoveGuild(final int guildId)
	{
		ThreadPoolManager.getInstance().scheduleGeneral(new Runnable()
		{
			@Override
			public void run()
			{
				if (getGuild(guildId) == null)
				{
					return;
				}
				if (getGuild(guildId).getDissolvingExpiryTime() != 0)
				{
					destroyGuild(guildId);
				}
			}
		}, Math.max(getGuild(guildId).getDissolvingExpiryTime() - System.currentTimeMillis(), 300000));
	}
	
	public boolean isAllyExists(String allyName)
	{
		for (MMOGuild guild : getGuilds())
		{
			if ((guild.getAllyName() != null) && guild.getAllyName().equalsIgnoreCase(allyName))
			{
				return true;
			}
		}
		return false;
	}
	
	public void storeguildswars(int guildId1, int guildId2)
	{
		MMOGuild guild1 = GuildTable.getInstance().getGuild(guildId1);
		MMOGuild guild2 = GuildTable.getInstance().getGuild(guildId2);
		
		if (!fireGuildWarStartListeners(guild1, guild2))
		{
			return;
		}
		
		guild1.setEnemyGuild(guild2);
		guild2.setAttackerGuild(guild1);
		guild1.broadcastGuildStatus();
		guild2.broadcastGuildStatus();
		try (Connection con = MMODatabaseFactory.getInstance().getConnection();
			PreparedStatement ps = con.prepareStatement("REPLACE INTO guild_wars (guild1, guild2, wantspeace1, wantspeace2) VALUES(?,?,?,?)"))
		{
			ps.setInt(1, guildId1);
			ps.setInt(2, guildId2);
			ps.setInt(3, 0);
			ps.setInt(4, 0);
			ps.execute();
		}
		catch (Exception e)
		{
			_log.log(Level.SEVERE, getClass().getSimpleName() + ": Error storing guild wars data.", e);
		}
		
		// SystemMessage msg = SystemMessage.getSystemMessage(SystemMessageId.WAR_WITH_THE_S1_GUILD_HAS_BEGUN);
		//
		SystemMessage msg = SystemMessage.getSystemMessage(SystemMessageId.GUILD_WAR_DECLARED_AGAINST_S1_IF_KILLED_LOSE_LOW_EXP);
		msg.addString(guild2.getName());
		guild1.broadcastToOnlineMembers(msg);
		// msg = SystemMessage.getSystemMessage(SystemMessageId.WAR_WITH_THE_S1_GUILD_HAS_BEGUN);
		// msg.addString(guild1.getName());
		// guild2.broadcastToOnlineMembers(msg);
		// guild1 declared guild war.
		msg = SystemMessage.getSystemMessage(SystemMessageId.GUILD_S1_DECLARED_WAR);
		msg.addString(guild1.getName());
		guild2.broadcastToOnlineMembers(msg);
	}
	
	public void deleteguildswars(int guildId1, int guildId2)
	{
		MMOGuild guild1 = GuildTable.getInstance().getGuild(guildId1);
		MMOGuild guild2 = GuildTable.getInstance().getGuild(guildId2);
		
		if (!fireGuildWarEndListeners(guild1, guild2))
		{
			return;
		}
		
		guild1.deleteEnemyGuild(guild2);
		guild2.deleteAttackerGuild(guild1);
		guild1.broadcastGuildStatus();
		guild2.broadcastGuildStatus();
		
		try (Connection con = MMODatabaseFactory.getInstance().getConnection();
			PreparedStatement ps = con.prepareStatement("DELETE FROM guild_wars WHERE guild1=? AND guild2=?"))
		{
			ps.setInt(1, guildId1);
			ps.setInt(2, guildId2);
			ps.execute();
		}
		catch (Exception e)
		{
			_log.log(Level.SEVERE, getClass().getSimpleName() + ": Error removing guild wars data.", e);
		}
		
		// SystemMessage msg = SystemMessage.getSystemMessage(SystemMessageId.WAR_WITH_THE_S1_GUILD_HAS_ENDED);
		SystemMessage msg = SystemMessage.getSystemMessage(SystemMessageId.WAR_AGAINST_S1_HAS_STOPPED);
		msg.addString(guild2.getName());
		guild1.broadcastToOnlineMembers(msg);
		msg = SystemMessage.getSystemMessage(SystemMessageId.GUILD_S1_HAS_DECIDED_TO_STOP);
		msg.addString(guild1.getName());
		guild2.broadcastToOnlineMembers(msg);
	}
	
	public void checkSurrender(MMOGuild guild1, MMOGuild guild2)
	{
		int count = 0;
		for (MMOGuildMember player : guild1.getMembers())
		{
			if ((player != null) && (player.getPlayerInstance().getWantsPeace() == 1))
			{
				count++;
			}
		}
		if (count == (guild1.getMembers().length - 1))
		{
			if (!fireGuildWarEndListeners(guild1, guild2))
			{
				return;
			}
			guild1.deleteEnemyGuild(guild2);
			guild2.deleteEnemyGuild(guild1);
			deleteguildswars(guild1.getGuildId(), guild2.getGuildId());
		}
	}
	
	private void restorewars()
	{
		MMOGuild guild1, guild2;
		try (Connection con = MMODatabaseFactory.getInstance().getConnection();
			Statement statement = con.createStatement();
			ResultSet rset = statement.executeQuery("SELECT guild1, guild2 FROM guild_wars"))
		{
			while (rset.next())
			{
				guild1 = getGuild(rset.getInt("guild1"));
				guild2 = getGuild(rset.getInt("guild2"));
				if ((guild1 != null) && (guild2 != null))
				{
					guild1.setEnemyGuild(rset.getInt("guild2"));
					guild2.setAttackerGuild(rset.getInt("guild1"));
				}
				else
				{
					_log.log(Level.WARNING, getClass().getSimpleName() + ": restorewars one of guilds is null guild1:" + guild1 + " guild2:" + guild2);
				}
			}
		}
		catch (Exception e)
		{
			_log.log(Level.SEVERE, getClass().getSimpleName() + ": Error restoring guild wars data.", e);
		}
	}
	
	/**
	 * Check for nonexistent alliances
	 */
	private void allianceCheck()
	{
		for (MMOGuild guild : _guilds.values())
		{
			int allyId = guild.getAllyId();
			if ((allyId != 0) && (guild.getGuildId() != allyId))
			{
				if (!_guilds.containsKey(allyId))
				{
					guild.setAllyId(0);
					guild.setAllyName(null);
					guild.changeAllyCrest(0, true);
					guild.updateGuildInDB();
					_log.info(getClass().getSimpleName() + ": Removed alliance from guild: " + guild);
				}
			}
		}
	}
	
	public List<MMOGuild> getGuildAllies(int allianceId)
	{
		final List<MMOGuild> guildAllies = new ArrayList<>();
		if (allianceId != 0)
		{
			for (MMOGuild guild : _guilds.values())
			{
				if ((guild != null) && (guild.getAllyId() == allianceId))
				{
					guildAllies.add(guild);
				}
			}
		}
		return guildAllies;
	}
	
	public void storeGuildScore()
	{
		for (MMOGuild guild : _guilds.values())
		{
			guild.updateGuildScoreInDB();
		}
	}
	
	/**
	 * Fires all the GuildWarListener.onWarStart() methods<br>
	 * Returns true if the guild war is allowed
	 * @param guild1
	 * @param guild2
	 * @return
	 */
	private boolean fireGuildWarStartListeners(MMOGuild guild1, MMOGuild guild2)
	{
		if (!guildWarListeners.isEmpty() && (guild1 != null) && (guild2 != null))
		{
			GuildWarEvent event = new GuildWarEvent();
			event.setGuild1(guild1);
			event.setGuild2(guild2);
			event.setStage(EventStage.START);
			for (GuildWarListener listener : guildWarListeners)
			{
				if (!listener.onWarStart(event))
				{
					return false;
				}
			}
		}
		return true;
	}
	
	/**
	 * Fires all the GuildWarListener.onWarEnd() methods<br>
	 * Returns true if the guild war end is allowed
	 * @param guild1
	 * @param guild2
	 * @return
	 */
	private boolean fireGuildWarEndListeners(MMOGuild guild1, MMOGuild guild2)
	{
		if (!guildWarListeners.isEmpty() && (guild1 != null) && (guild2 != null))
		{
			GuildWarEvent event = new GuildWarEvent();
			event.setGuild1(guild1);
			event.setGuild2(guild2);
			event.setStage(EventStage.END);
			for (GuildWarListener listener : guildWarListeners)
			{
				if (!listener.onWarEnd(event))
				{
					return false;
				}
			}
		}
		return true;
	}
	
	/**
	 * Adds a guild war listener
	 * @param listener
	 */
	public static void addGuildWarListener(GuildWarListener listener)
	{
		if (!guildWarListeners.contains(listener))
		{
			guildWarListeners.add(listener);
		}
	}
	
	/**
	 * Removes a guild war listener
	 * @param listener
	 */
	public static void removeGuildWarListener(GuildWarListener listener)
	{
		guildWarListeners.remove(listener);
	}
	
	public static GuildTable getInstance()
	{
		return SingletonHolder._instance;
	}
	
	private static class SingletonHolder
	{
		protected static final GuildTable _instance = new GuildTable();
	}
}
